/*
 *****************************************************************************
 ** Module	:	dd
 ** Date: 2009-11-19				Time: 17:59:11
 ** Author: Wang Bo (Brain Wang)  2009
 *****************************************************************************
 ********************* CVS Change History ************************************
 * $Id: DisasterPreparedStatement.java,v 1.1 2012/07/19 06:07:28 sunhy Exp $
 * $Log: DisasterPreparedStatement.java,v $
 * Revision 1.1  2012/07/19 06:07:28  sunhy
 * *** empty log message ***
 *
 * Revision 1.2  2011/07/18 01:27:24  sunhy
 * 修改String类型为空时，值为""
 * 修改解析部分，改为 split方式，支持空字符串值
 *
 * Revision 1.1.1.1  2011/06/06 08:06:40  cvsadmin
 * no message
 *
 * Revision 1.3  2011/06/02 06:29:56  sunhy
 * *** empty log message ***
 *
 * Revision 1.2  2011/04/06 02:54:12  sunhy
 * for hss
 *
 * Revision 1.1  2010/03/12 07:16:27  wangbo
 * create
 *
 *****************************************************************************
 */
package com.hp.bon.jdbc;

import static com.hp.bon.jdbc.DisasterJdbcDriver.LOG;

import java.lang.reflect.*;
import java.sql.*;
import java.util.logging.*;
import java.util.*;
import java.text.*;

/**
 *
 */
public class DisasterPreparedStatement implements InvocationHandler {
	private static final char PARA_SEPERATOR = '|';

	public DisasterPreparedStatement(DisasterConnection conn,
			PreparedStatement st1, String sql) {
		_st1 = st1;
		_conn = conn;
		_sql = sql;
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object result = null;
		try {
			// if (LOG.isLoggable(Level.FINE))
			// LOG.fine("DisasterPreparedStatement before method " +
			// method.getName());
			if ("execute".equals(method.getName()))
				result = execute(method, args);
			else if ("executeUpdate".equals(method.getName()))
				result = executeUpdate(method, args);
			else if (method.getName().startsWith("set")
					&& method.getDeclaringClass().equals(
							PreparedStatement.class))
				result = setParameter(method, args);
//			else if (method.getName().equals("addBatch")) {
//				throw new SQLException(
//						"Batch operations were not supported yet!");
//			}  
			else
				result = method.invoke(_st1, args);
		} catch (InvocationTargetException e) {
			throw e.getTargetException();
		} catch (SQLException eSQL) {
			throw eSQL;
		} catch (Exception e) {
			throw new RuntimeException("unexpected invocation exception: "
					+ e.getMessage());
		} finally {
			// if (LOG.isLoggable(Level.FINE))
			// LOG.fine("DisasterPreparedStatement after method " +
			// method.getName());
		}
		return result;
	}

	private Object setParameter(Method method, Object[] args)
			throws InvocationTargetException, IllegalAccessException,
			SQLException {
		Object res = method.invoke(_st1, args);
		ParameterType t = null;
		try {
			t = ParameterType.valueOf(method.getName().substring(3));
		} catch (IllegalArgumentException e) {
			return res;
		}
		Para p = new Para();
		p.type = t;
		p.index = (Integer) args[0];
		p.value = args[1];
		p.sqlType = -1;
		switch (t) {
		case BigDecimal:
		case Byte:
		case Double:
		case Float:
		case Int:
		case Long:
		case Short:
		case String:
			p.sqlValue = Utils.encodeStr(p.value.toString());
			// 2011-04-19 sunhy
			// 2011-07-15 sunhy 为空时
			if (null == p.sqlValue) {
				p.sqlValue = "";
			}
			break;
		case Object:// void setObject(int parameterIndex, Object x, int
					// targetSqlType, int scaleOrLength)
			p.sqlValue = p.value.toString();
			if (args.length == 3)
				p.sqlType = (Integer) args[2];
			if (args.length > 3)
				throw _conn.unsupportException(method, args);
			break;
		case Date:// setDate(int parameterIndex, java.sql.Date x, Calendar cal)
		case Time:// setTime(int parameterIndex, java.sql.Time x, Calendar cal)
		case Timestamp:// setTimeStamp(int parameterIndex, java.sql.Time x,
						// Calendar cal)
			if (args.length > 2)
				throw _conn.unsupportException(method, args);
			p.sqlValue = FORMAT_TIME.format(p.value);
			break;
		case Null:// setNull (int parameterIndex, int sqlType, String typeName)
			p.sqlType = (Integer) args[1];
			// 2011-04-19
			p.sqlValue = null;
			break;
		default:
			throw _conn.unsupportException(method, args);
		}
		// 2011-03-17 sunhy
		// if (p.sqlValue.indexOf(PARA_SEPERATOR) >= 0)
		// throw new SQLException("Value must not include | in para:" + p);
		if (!_listParas.contains(p)) {
			_listParas.add(p);
			if (LOG.isLoggable(Level.FINE))
				LOG.fine("SetParameter " + p);
		}
		return res;
	}

	private Object executeUpdate(Method method, Object[] args)
			throws InvocationTargetException, IllegalAccessException,
			SQLException {
		Object res1 = method.invoke(_st1, args);
		int effectedRows = (Integer) res1;
		if ((args != null) && (args.length > 1))
			throw _conn.unsupportException(method, args);
		// generate disaster data
		String tid = _conn.getTransactionID();
		// parameters
		StringBuilder paras = new StringBuilder();
		// ParameterMetaData pmd = _st1.getParameterMetaData();//Exception in
		// oracle
		for (Para p : _listParas) {
			paras.append(p.index).append(PARA_SEPERATOR);
			paras.append(p.type).append(PARA_SEPERATOR);
			paras.append(p.sqlValue).append(PARA_SEPERATOR);
			paras.append(p.sqlType).append(PARA_SEPERATOR);
			paras.append(PARA_SEPERATOR);
		}
		if (LOG.isLoggable(Level.FINE))
			LOG.fine("DiasterPreparedStatement executeUpdate: TID=" + tid
					+ ", SQL=" + _sql + " Parameters=" + paras.toString());
		// table name, record update
		_conn.logDisaster(tid, _sql, paras.toString(), effectedRows);
		// 2011-04-20 sunhy
		_listParas.clear();
		// commit?
		if (_conn.isOrgAutoCommit()) {
			_st1.getConnection().commit();
			if (LOG.isLoggable(Level.FINE))
				LOG.fine("DiasterPreparedStatement executeUpdate: commit change by TID:"
						+ tid);
			DisasterJdbcDriver.tryReplication();
		}
		return res1;
	}

	private Object execute(Method method, Object[] args)
			throws InvocationTargetException, IllegalAccessException,
			SQLException {
		Boolean res1 = (Boolean) method.invoke(_st1, args);
		if (res1)
			return res1;// Query
		// generate disaster data
		String tid = _conn.getTransactionID();
		// parameters
		StringBuilder paras = new StringBuilder();
		// ParameterMetaData pmd = _st1.getParameterMetaData();//Exception in
		// oracle
		for (Para p : _listParas) {
			paras.append(p.index).append(PARA_SEPERATOR);
			paras.append(p.type).append(PARA_SEPERATOR);
			paras.append(p.sqlValue).append(PARA_SEPERATOR);
			paras.append(p.sqlType).append(PARA_SEPERATOR);
			paras.append(PARA_SEPERATOR);
		}
		if (LOG.isLoggable(Level.FINE))
			LOG.fine("DiasterPreparedStatement executeUpdate: TID=" + tid
					+ ", SQL=" + _sql + " Parameters=" + paras.toString());
		// table name, record update
		_conn.logDisaster(tid, _sql, paras.toString(), _st1.getUpdateCount());
		// 2011-04-20 sunhy
		_listParas.clear();
		// commit?
		if (_conn.isOrgAutoCommit()) {
			_st1.getConnection().commit();
			if (LOG.isLoggable(Level.FINE))
				LOG.fine("DiasterPreparedStatement executeUpdate: commit change by TID:"
						+ tid);
			DisasterJdbcDriver.tryReplication();
		}
		return res1;
	}

	private PreparedStatement _st1;
	private DisasterConnection _conn;
	private String _sql;

	static public enum ParameterType {
		Array, AsciiStream, BigDecimal, BinaryStream, Blob, Boolean, Byte, Bytes, CharacterStream, Clob, Date, Double, Float, Int, Long, NCharacterStream, NClob, NString, Null, Object, Ref, RowId, Short, SQLXML, String, Time, Timestamp, UnicodeStream, URL
	}

	static private class Para {
		int index;
		ParameterType type;
		Object value;
		String sqlValue;
		int sqlType;

		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof Para))
				return false;
			return index == ((Para) obj).index;
		}

		@Override
		public String toString() {
			return "Para{" + "index=" + index + ", type=" + type + ", value="
					+ value + ", sqlValue='" + sqlValue + '\'' + ", sqlType="
					+ sqlType + '}';
		}
	}

	private List<Para> _listParas = new ArrayList<Para>();
	static private SimpleDateFormat FORMAT_TIME = new SimpleDateFormat(
			"yyyyMMddHHmmss");
}
